﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace IsisCubeDemo
{
    public class Ball
    {
        private Vector3 _position;
        public Vector3 position
        {
            get
            {
                return _position;
            }
            set
            {
                _position = value;

                if (value.X > SceneManager.planeDistanceFromOrigin)
                    _position.X = SceneManager.planeDistanceFromOrigin - radius;
                else if (value.X < -SceneManager.planeDistanceFromOrigin)
                    _position.X = -(SceneManager.planeDistanceFromOrigin - radius);
                if (value.Y > SceneManager.planeDistanceFromOrigin)
                    _position.Y = SceneManager.planeDistanceFromOrigin - radius;
                else if (value.Y < -SceneManager.planeDistanceFromOrigin)
                    _position.Y = -(SceneManager.planeDistanceFromOrigin - radius);
                if (value.Z > SceneManager.planeDistanceFromOrigin)
                    _position.Z = SceneManager.planeDistanceFromOrigin - radius;
                else if (value.Z < -SceneManager.planeDistanceFromOrigin)
                    _position.Z = -(SceneManager.planeDistanceFromOrigin - radius);

            }
        }
        private Vector3 _direction;
        public Vector3 direction
        {
            get
            {
                return _direction;
            }
            set
            {
                value.Normalize();
                _direction = value;
            }
        }

        public Vector3 color;
        private int _number;
        public int number
        {
            get { return _number; }
            set
            {
                if (value < 0)
                    _number = 0;
                else if (value > 100)
                    _number = 99;
                else
                    _number = value;
            }
        }

        public float velocity = 0.3f;
        public float radius = 1.0f;
        private int id;
        public BoundingSphere boundingSphere;

        public Ball(Vector3 origin, Vector3 direction, Vector3 color, int number, int id)
        {
            this.position = origin;
            this.direction = direction;
            this.color = color;
            this.number = number;
            this.id = id;
            boundingSphere = new BoundingSphere(origin, this.radius);
        }
        public void set(Vector3 origin, Vector3 direction, Vector3 color, int number, int id)
        {
            this.position = origin;
            this.direction = direction;
            this.color = color;
            this.number = number;
            this.id = id;
            this.boundingSphere = new BoundingSphere(origin, this.radius);
        }
        public void update(Plane[] planes)
        {
            this.position = this.position + (this.direction * this.velocity);
            boundingSphere = new BoundingSphere(this.position, this.radius);
            Vector3 direction = Vector3.Zero;
            for (int i = 0; i < planes.Length; i++)
            {
                if (this.boundingSphere.Intersects((Plane)planes[i]).Equals(PlaneIntersectionType.Intersecting))
                {
                    direction = this.direction - 2 * (planes[i].DotNormal(this.direction)) * planes[i].Normal;
                    this.direction = direction;
                    break;

                }

            }
            this.color = new Vector3((this.color.X + 0.001f) % 1.0f, (this.color.Y + 0.001f) % 1.0f, (this.color.Z + 0.001f) % 1.0f);
            this.number = (this.number + 1) % 100;

        }


    }

    public class Cube
    {
        public Vector3 position;
        public Vector3 color;
        public int number;
        public BoundingBox boundingBox;
        public int id;

        public Cube(Vector3 origin, Vector3 color, int number, int id)
        {
            this.position = origin;
            this.color = color;
            this.number = number;
            this.id = id;
            this.boundingBox = new BoundingBox(new Vector3(origin.X - SceneManager.planeDistanceFromOrigin / 10,
                                                            origin.Y - SceneManager.planeDistanceFromOrigin / 10,
                                                            origin.Z - SceneManager.planeDistanceFromOrigin / 10),
                                                new Vector3(origin.X + SceneManager.planeDistanceFromOrigin / 10,
                                                            origin.Y + SceneManager.planeDistanceFromOrigin / 10,
                                                            origin.Z + SceneManager.planeDistanceFromOrigin / 10));


        }
        public void set(Vector3 position, Vector3 color, int number, int id)
        {
            this.position = position;
            this.color = color;
            this.number = number;
            this.id = id;
        }



    }
    class SceneManager : DrawableGameComponent
    {
        Model sphere, box;
        BoundingBox boundingBox;
        private Plane[] planes = new Plane[6];
        // Initialize an array of indices for the box. 12 lines require 24 indices
        short[] bBoxIndices = {
	                            0, 1, 1, 2, 2, 3, 3, 0, // Front edges
	                            4, 5, 5, 6, 6, 7, 7, 4, // Back edges
	                            0, 4, 1, 5, 2, 6, 3, 7 // Side edges connecting front and back
                            };
        Vector3[] corners;
        VertexPositionColor[] primitiveList;
        BasicEffect boxEffect;

        CameraComponent camera;
        //ConcurrentDictionary<int, Ball> balls;
        //ConcurrentDictionary<int, Cube> cubes;
        Random random = new Random();
        SpriteFont spriteFont;
        SpriteBatch spriteBatch;
        IsisNode isisNode;
        public static float planeDistanceFromOrigin = 50.0f;

        public SceneManager(Game game)
            : base(game)
        {
            //balls = new ConcurrentDictionary<int, Ball>();
            //cubes = new ConcurrentDictionary<int, Cube>();   
        }
        public override void Initialize()
        {
            camera = (CameraComponent)Game.Components.First(c => c.GetType() == typeof(CameraComponent));
            isisNode = (IsisNode)Game.Components.First(c => c.GetType() == typeof(IsisNode));
            planes[0] = new Plane(new Vector3(0.0f, 1.0f, 0.0f), -planeDistanceFromOrigin); // bottom
            planes[1] = new Plane(new Vector3(0.0f, -1.0f, 0.0f), -planeDistanceFromOrigin); //up
            planes[2] = new Plane(new Vector3(-1.0f, 0.0f, 0.0f), -planeDistanceFromOrigin); //right
            planes[3] = new Plane(new Vector3(1.0f, 0.0f, 0.0f), -planeDistanceFromOrigin); //left
            planes[4] = new Plane(new Vector3(0.0f, 0.0f, -1.0f), -planeDistanceFromOrigin); // front
            planes[5] = new Plane(new Vector3(0.0f, 0.0f, 1.0f), -planeDistanceFromOrigin); //back

            boundingBox = new BoundingBox(new Vector3(-planeDistanceFromOrigin, -planeDistanceFromOrigin, -planeDistanceFromOrigin), new Vector3(planeDistanceFromOrigin, planeDistanceFromOrigin, planeDistanceFromOrigin));
            corners = boundingBox.GetCorners();
            primitiveList = new VertexPositionColor[corners.Length];
            // Assign the 8 box vertices
            for (int i = 0; i < corners.Length; i++)
            {
                primitiveList[i] = new VertexPositionColor(corners[i], Color.White);
            }

            boxEffect = new BasicEffect(Game.GraphicsDevice);

            base.Initialize();
        }
        protected override void LoadContent()
        {
            sphere = Game.Content.Load<Model>(@"Models\sphere");
            box = Game.Content.Load<Model>(@"Models\box");
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // Load the sprite font. The sprite font has a 3 pixel outer glow
            // baked into it so we need to decrease the spacing so that the
            // SpriteFont will render correctly.

            spriteFont = Game.Content.Load<SpriteFont>(@"Fonts\Arial-Bold-12pt-OuterGlow-3px");
            spriteFont.Spacing = -4.0f;

            base.LoadContent();
        }
        public void CreateRandomBall(Vector3 origin)
        {
            int ranX = random.Next(-10, 10);
            int ranY = random.Next(-10, 10);
            int ranZ = random.Next(-10, 10);

            isisNode.putBall(origin, new Vector3(ranX, ranY, ranZ), new Vector3(Math.Abs(ranX) / 10.0f, Math.Abs(ranY) / 10.0f, Math.Abs(ranZ) / 10.0f),
                        random.Next(0, 99),
                        isisNode.getBallCount() + IsisNode.maxCubeNumber);
        }
        public void CreateBall(Vector3 origin, int id)
        {
            int ranX = random.Next(-10, 10);
            int ranY = random.Next(-10, 10);
            int ranZ = random.Next(-10, 10);

            isisNode.putBall(origin, new Vector3(ranX, ranY, ranZ), new Vector3(Math.Abs(ranX) / 10.0f, Math.Abs(ranY) / 10.0f, Math.Abs(ranZ) / 10.0f),
                        random.Next(0, 99),
                        id);
        }
        public void UpdateRandomeBall()
        {
            int count = isisNode.getBallCount();
            if (count > 0)
            {
                int id = random.Next(0, count);
                id = IsisNode.maxCubeNumber + id;
                Ball ball = isisNode.getBalls().ToDictionary(x => x.Key, x => x.Value)[id].makeBallFromState();
                //ball.update(planes);
                Vector3 position, direction, color;
                int number;
                color = new Vector3((ball.color.X + 0.001f) % 1.0f, (ball.color.Y + 0.001f) % 1.0f, (ball.color.Z + 0.001f) % 1.0f);
                number = (ball.number + 1) % 100;
                position = ball.position + (ball.direction * ball.velocity);
                direction = ball.direction;
                BoundingSphere bs = new BoundingSphere(position, ball.radius);
                for (int i = 0; i < planes.Length; i++)
                {
                    if (bs.Intersects((Plane)planes[i]).Equals(PlaneIntersectionType.Intersecting))
                    {
                        direction = ball.direction - 2 * (planes[i].DotNormal(ball.direction)) * planes[i].Normal;
                        break;
                    }

                }
                isisNode.putBall(position, direction, color, number, id);

                foreach (KeyValuePair<int, DemoObjectState> pair in isisNode.getCubes().ToArray())
                {
                    DemoObjectState cubeState = pair.Value;
                    if (bs.Intersects(new BoundingBox(new Vector3(cubeState.positionX - SceneManager.planeDistanceFromOrigin / 10,
                                                            cubeState.positionY - SceneManager.planeDistanceFromOrigin / 10,
                                                            cubeState.positionZ - SceneManager.planeDistanceFromOrigin / 10),
                                                new Vector3(cubeState.positionX + SceneManager.planeDistanceFromOrigin / 10,
                                                            cubeState.positionY + SceneManager.planeDistanceFromOrigin / 10,
                                                            cubeState.positionZ + SceneManager.planeDistanceFromOrigin / 10))))
                    {
                        isisNode.putCube(new Vector3(cubeState.positionX, cubeState.positionY, cubeState.positionZ), Vector3.Zero, color, number, cubeState.id);
                        break;
                    }

                }
            }
            else
            {
                CreateBall(Vector3.Zero, IsisNode.maxCubeNumber + count);
            }
        }

        void DrawBoundingBox()
        {
            boxEffect.World = Matrix.Identity;
            boxEffect.View = camera.ViewMatrix;
            boxEffect.Projection = camera.ProjectionMatrix;
            boxEffect.TextureEnabled = false;
            // Draw the box with a LineList
            foreach (EffectPass pass in boxEffect.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserIndexedPrimitives(
                    PrimitiveType.LineList, primitiveList, 0, 8,
                    bBoxIndices, 0, 12);
            }
        }
        void DrawScene()
        {
            Dictionary<int, DemoObjectState> objs = isisNode.getDemoObjects();
            if (objs != null)
            {


                foreach ( DemoObjectState obj in objs.Values)
                {
                Vector3 position = new Vector3(obj.positionX, obj.positionY, obj.positionZ);
                    Model model;
                    if (obj.id < IsisNode.maxCubeNumber)
                        model = box;
                    else
                        model = sphere;

                    foreach (ModelMesh mesh in model.Meshes)
                    {
                        foreach (BasicEffect effect in mesh.Effects)
                        {
                            effect.World = Matrix.CreateTranslation(position);
                            effect.View = camera.ViewMatrix;
                            effect.Projection = camera.ProjectionMatrix;
                            effect.DiffuseColor = new Vector3(obj.colorR, obj.colorG, obj.colorB);
                            effect.EnableDefaultLighting();
                        }
                        mesh.Draw();
                    }
                    Vector3 screenspace;
                    Vector2 textposition;
                    spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
                    screenspace = GraphicsDevice.Viewport.Project(position, camera.ProjectionMatrix, camera.ViewMatrix, Matrix.Identity);
                    textposition = new Vector2(screenspace.X, screenspace.Y);
                    spriteBatch.DrawString(spriteFont, obj.number.ToString(), textposition, Color.White);
                    spriteBatch.End();
                }
            }
        }


        public override void Draw(GameTime gameTime)
        {

            DrawBoundingBox();
            DrawScene();
            //Game.GraphicsDevice.RenderState.DepthBufferEnable = true;
            base.Draw(gameTime);
        }
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
        }


    }

}
